package com.biz.primus.ms.oms.service.allocation;

import com.biz.primus.common.utils.StringUtil;
import com.biz.primus.model.stock.vo.MeetHomeStockReqVo;
import com.biz.primus.model.stock.vo.MeetStockRespVo;
import com.biz.primus.model.stock.vo.ProductQuantityPair;
import com.biz.primus.model.stock.vo.ProvinceStockReqVo;
import com.biz.primus.ms.base.service.AbstractBaseService;
import com.biz.primus.ms.oms.dao.po.OmsOrder;
import com.biz.primus.ms.oms.dao.po.OmsOrderItem;
import com.biz.primus.ms.oms.dao.po.logs.AllocationOrderLogs;
import com.biz.primus.ms.oms.dao.repository.OmsOrderRepository;
import com.biz.primus.ms.oms.dao.repository.logs.AllocationOrderLogsRepository;
import com.biz.primus.ms.oms.feign.StockFeignClient;
import com.biz.primus.ms.oms.service.allocation.validator.OrderValidator;
import com.biz.primus.ms.oms.service.allocation.validator.VerificationResult;
import com.biz.primus.ms.oms.service.core.OrderStateService;
import com.biz.primus.ms.oms.service.core.OrderStateService.AllocationStateRequestVo;
import com.biz.primus.ms.oms.service.core.OrderStateService.OrderStateChangeRequest;
import com.biz.primus.ms.oms.service.logs.OrderLogsService;
import com.biz.primus.ms.oms.vo.AllocationRequestVo;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author yanweijin
 * @date 2017/11/6
 */

@Service
@Slf4j
public class OrderAllocationServiceImpl extends AbstractBaseService implements OrderAllocationService{

	@Autowired
	private OmsOrderRepository omsOrderRepository;

	@Autowired
	private OrderLogsService orderLogsService;

	@Autowired
	private OrderStateService orderStateService;

	private Map<String, OrderValidator> validators;

	@Autowired
	private StockFeignClient stockFeignClient;

	@Autowired
	private AllocationOrderLogsRepository allocationOrderLogsRepository;

	private String[] defaultValidatorStack = {
			"product","amount","consignee","userDefined"
	};

	/**
	 * 将注入的订单验证器转换为map
	 * @param validators
	 */
	@Autowired
	public void setValidators(List<OrderValidator> validators){
		this.validators = Maps.uniqueIndex(validators, OrderValidator::getValidatorName);
	}


	/**
	 *  0. 校验订单数据,如果数据异常,不进入自动分单流程
	 *
	 *  1. 如果请求中有targetDepotCode参数,强制使用该门店(客服修改的场合)
	 *
	 *  2. 如果订单当前没有指定门店(普通订单正常自动分单),或者请求中要求强制重新分单(门店申请改派的场合),则进入自动分单流程
	 *
	 *  3. 如果订单已经指定了depotCode参数,则使用该参数(新立即送订单)
	 *
	 *
	 */
	@Override
	public void allocateOrder(AllocationRequestVo req){
		OmsOrder order = findOrder(req);
		//检查订单数据
		String errorInfo = validateOrder(defaultValidatorStack,order)
				.filter(VerificationResult::isHasError)
				.map(VerificationResult::getErrorInfo)
				.filter(StringUtils::isNotBlank)
				.collect(Collectors.joining());

		if(StringUtils.isNotBlank(errorInfo)){
			//记录分单日志
			String msg = "订单参数不正确,转入人工分单, info=" + errorInfo;
			orderLogsService.logOrderAllocation(order.getOrderCode(), order.getDepotCode(), null, msg, "system");
			order.setSelfRemark(order.getSelfRemark()+"|||"+msg);
			omsOrderRepository.save(order);
			//记录系统日志
			log.warn("oms-allocator: 订单参数不正确,停止自动分单, info={}",errorInfo);
			return;
		}
		AllocationResult result;

		if(req.getTargetDepotCode() != null){
			//手动分单,有目标门店
			//场景: 客服修改门店
			result = new AllocationResult(req.getTargetDepotCode(), true);

		}else if(order.getDepotCode() == null || req.isForceAutoAllocation()){
			//自动分单,没有目标门店,或强制自动分单
			//场景1: 门店申请改派
			//场景2: 订单正常自动分单
			result = autoAllocateDepot(order, req.getExpectDepotCode());
		}else{
			//自动分单,有目标门店
			//场景: 立即送订单(指派了门店)
			result = new AllocationResult(order.getDepotCode(), false);
		}
		AllocationStateRequestVo stateRequestVo = new AllocationStateRequestVo(
				OrderStateChangeRequest.builder().order(order).operator(req.getOperator()).build(),
				result.depot,
				result.dispatchingMark
		);
		orderStateService.allocateOrder(stateRequestVo);
		orderLogsService.logOrderAllocation(order.getOrderCode(), order.getDepotCode(), result.depot, "分单成功:"+result, "system");


	}

	private OmsOrder findOrder(AllocationRequestVo req){
		return req.getOrder()==null? omsOrderRepository.findOne(req.getOrderId()): req.getOrder();
	}

	@AllArgsConstructor
	@ToString
	private class AllocationResult{
		String depot;
		boolean dispatchingMark;
	}

	/**
	 * 自动分单流程
	 *
	 *  0. 检查已经分配过的门店并过滤
	 *
	 *  1. 如果本省有门店,则取最近10km范围内完全符合库存的门店
	 *
	 *  2. 太原市中心店(三个)中,满足库存且库存最多的(购买的商品数量加在一起最多)
	 *
	 *  3. 如果传递了expectDepotCode,就使用指定门店,否则,如果本省有门店,使用省内当前收货地址最近门店
	 *
	 *  4. 以上都不满足,分到太原中心店库存最多的门店
	 *
	 *
	 * @param order 订单
	 * @param expectedDepotCode 分配的门店编号
	 * @return
	 */
	private AllocationResult autoAllocateDepot(OmsOrder order, String expectedDepotCode){

		//查询分单日志, 聚合出已经分过的门店
		List<AllocationOrderLogs>  logs = allocationOrderLogsRepository.findByOrderCode(order.getOrderCode());
		List<String> depotCodeList = logs.stream().map(AllocationOrderLogs::getTargetDepotCode)
				.filter(StringUtils::isNotBlank).map(StringUtils::trim).distinct()
				.collect(Collectors.toList());


		ProvinceStockReqVo provinceStockReqVo=new ProvinceStockReqVo();
		provinceStockReqVo.setLon(order.getConsignee().getLon());
		provinceStockReqVo.setLat(order.getConsignee().getLat());
		provinceStockReqVo.setDistance(10);
		provinceStockReqVo.setProvinceId(order.getConsignee().getProvinceId());
		List<OmsOrderItem> omsOrderItems = order.getItems();
		List<ProductQuantityPair> productQuantityPairs = omsOrderItems.stream().map(reqVo -> {
			ProductQuantityPair productQuantityPair=new ProductQuantityPair();
			productQuantityPair.setProductCode(reqVo.getProductCode());
			productQuantityPair.setQuantity(reqVo.getQuantity());
			return productQuantityPair;
		}).collect(Collectors.toList());
		//设置商品和数量
		provinceStockReqVo.setPair(productQuantityPairs);
		provinceStockReqVo.setNotExpect(depotCodeList);
		//系统分单
		//1. 检查本省内10km内的门店库存
		MeetStockRespVo meetStockRespVo = stockFeignClient.getMeetByProvince(provinceStockReqVo);
		if(meetStockRespVo.getFound()){
			return new AllocationResult(meetStockRespVo.getDepotCode(),false);
		}
        //2. 检查太原市中心店的库存
		MeetHomeStockReqVo meetHomeStockReqVo=new MeetHomeStockReqVo();
		meetHomeStockReqVo.setMeet(true);
		meetHomeStockReqVo.setPair(productQuantityPairs);
		meetHomeStockReqVo.setNotExpect(depotCodeList);
		MeetStockRespVo meetStockRespVo2 = stockFeignClient.getMeetByHome(meetHomeStockReqVo);
		if(meetStockRespVo2.getFound()){
			return new AllocationResult(meetStockRespVo2.getDepotCode(),false);
		}
		// ===== 以下流程,调拨标识为true

        //3. 如果depotCode不为空,使用该门店
        if(!StringUtil.isNullOrEmpty(expectedDepotCode)){
            return new AllocationResult(expectedDepotCode, true);
        }

        //4. 如果本省有门店,使用省内最近门店
		provinceStockReqVo.setDistance(null);
		MeetStockRespVo meetStockRespVo3 = stockFeignClient.getMeetByProvince(provinceStockReqVo);
        if(meetStockRespVo3.getFound()){
        	return new AllocationResult(meetStockRespVo3.getDepotCode(), true);
		}

		//5. 使用太原市中心店
		meetHomeStockReqVo.setMeet(false);
		MeetStockRespVo meetStockRespVo4 = stockFeignClient.getMeetByHome(meetHomeStockReqVo);
        return new AllocationResult(meetStockRespVo4.getDepotCode(), true);
	}


	private Stream<VerificationResult> validateOrder(String[] validatorStack, OmsOrder order) {
		return Arrays.stream(validatorStack).map(validators::get).map(v->v.validate(order));
	}

}
